<template>
    <div class="a-input-slider" :class="[
        `color-${formItemHook.myColor}`,
        `size-${formItemHook.mySize}`,
        { sharp: formItemHook.mySharp },
        { disabled: formItemHook.myDisabled },
    ]" :tabindex="formItemHook.myDisabled ? 'unset' : '0'" ref="elRef">
        <div class="slider-container" ref="sliderContainerRef">
            <a-tooltip :tips="`${props.value}`" :popShown="popShown" :size="formItemHook.mySize" :sharp="formItemHook.mySharp">
                <div class="slider" ref="slider" :style="{ left: `${sliderLeft}` }">
                    <div class="slider-bg"></div>
                </div>
            </a-tooltip>
            <div class="min-line line"></div>
            <div class="max-line line"></div>
            <span class="min-text" :class="[`c-font-size-${formItemHook.mySize}`]">{{
                props.minValue
            }}</span>
            <span class="max-text" :class="[`c-font-size-${formItemHook.mySize}`]">{{
                props.maxValue
            }}</span>
        </div>
    </div>
</template>

<style lang="scss" scoped>
@import "../scss/vars";

.a-input-slider {
    width: 200px;
    box-sizing: border-box;
    overflow: hidden;
    outline: none;

    &.disabled {
        opacity: 0.5;
        cursor: not-allowed;
    }

    @each $colorPackageName, $colorPackageValue in $colorPackages {
        $frontColor: map-get($colorPackageValue, "frontColor");
        $bgColor: map-get($colorPackageValue, "bgColor");

        &.color-#{$colorPackageName} {
            color: $frontColor;
            background-color: $bgColor;

            .slider-bg,
            .line {
                background-color: $frontColor;
            }
        }
    }

    @each $sizePackageName, $sizePackageValue in $sizePackages {
        &.size-#{$sizePackageName} {
            padding-left: map-get($sizePackageValue, "space");
            padding-right: map-get($sizePackageValue, "space");
            height: map-get($sizePackageValue, "size");

            &:not(.sharp) {
                border-radius: map-get($sizePackageValue, "borderRadius");
            }
        }
    }

    .slider-container {
        position: relative;
        width: 100%;
        height: 100%;

        .slider {
            position: absolute;
            top: 0;
            left: 0;
            width: 1px;
            height: 100%;

            .slider-bg {
                width: 5px;
                height: 100%;
                margin-left: -2px;
                box-shadow: 5px 0 10px #333;
            }
        }

        .min-text {
            position: absolute;
            left: 0;
            bottom: 0;
            pointer-events: none;
        }

        .max-text {
            position: absolute;
            right: 0;
            bottom: 0;
            pointer-events: none;
        }

        .line {
            position: absolute;
            width: 1px;
            height: 50%;
            top: 0;
        }

        .min-line {
            left: 0;
        }

        .max-line {
            left: 100%;
        }
    }
}
</style>

<script setup lang="ts">
import utils from "@/common/utils"
import { useFormItem } from '@/hooks/useFormItem'
import { useInput } from '@/hooks/useInput'
import { InputPropsType, Rule_max, Rule_min } from '@/types'
import { Decimal } from 'decimal.js'
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'

interface PropsType extends InputPropsType<number | undefined, Rule_min | Rule_max> {
    minValue?: number
    maxValue?: number
    step?: number
}
const props = withDefaults(defineProps<PropsType>(), {
    value: undefined,
    color: undefined,
    disabled: undefined,
    size: undefined,
    sharp: undefined,
    rules: () => [],
    isInner: false,
    minValue: 0,
    maxValue: 100,
    step: 1,
})

const methods = {
    input(val?: number) {
        emit('update:value', val)
    },
    addEventListener() {
        const el = elRef.value!
        el.addEventListener('mousedown', methods.onRootMousedown)
        el.addEventListener('focus', methods.onRootFocus)
        el.addEventListener('blur', methods.onRootBlur)
        el.addEventListener('mouseover', methods.onRootMouseover)
        el.addEventListener('mouseleave', methods.onRootMouseleave)
        window.document.addEventListener('mousemove', methods.onDocumentMousemove)
        window.document.addEventListener('mouseup', methods.onDocumentMouseup)
        window.document.addEventListener('keydown', methods.onDocumentKeydown)
    },
    removeEventListener() {
        window.document.removeEventListener('mousemove', methods.onDocumentMousemove)
        window.document.removeEventListener('mouseup', methods.onDocumentMouseup)
        window.document.removeEventListener('keydown', methods.onDocumentKeydown)
    },
    onRootMousedown(e: MouseEvent) {
        if (formItemHook.value.myDisabled) {
            return
        }
        if (e.button !== 0) {
            return
        }
        state.mouseDown = true

        methods.calcValue(e.clientX)
    },
    onDocumentMousemove(e: MouseEvent) {
        if (!state.mouseDown) {
            return
        }
        e.preventDefault()
        methods.calcValue(e.clientX)
    },
    onDocumentMouseup(_e: MouseEvent) {
        state.mouseDown = false
    },
    onRootFocus() {
        state.focused = true
    },
    onRootBlur() {
        state.focused = false
    },
    onRootMouseover() {
        state.mouseIn = true
    },
    onRootMouseleave() {
        state.mouseIn = false
    },
    onDocumentKeydown(e: KeyboardEvent) {
        if (!state.focused) {
            return
        }
        if (e.keyCode === 37) {
            e.preventDefault()
            let value = new Decimal(props.value ?? 0).minus(new Decimal(props.step)).toNumber()
            methods.input(Math.max(value, props.minValue))
        } else if (e.keyCode === 39) {
            e.preventDefault()
            let value = new Decimal(props.value ?? 0).plus(new Decimal(props.step)).toNumber()
            methods.input(Math.min(value, props.maxValue))
        }
    },
    checkAndSetContainerPositionAndSize() {
        let containerRect = sliderContainerRef.value!.getBoundingClientRect()
        state.containerLeft = containerRect.left
        state.containerRight = containerRect.right
    },
    calcValue(mouseClientX: number) {
        let realValue = (mouseClientX - minClientX.value) * (props.maxValue - props.minValue) / (maxClientX.value - minClientX.value) + props.minValue
        if (realValue < props.minValue) {
            methods.input(props.minValue)
        } else if (realValue > props.maxValue) {
            methods.input(props.maxValue)
        } else {
            let outputValue = Math.round(realValue / props.step) * props.step
            let fixedOutputValue = Number(outputValue.toFixed(decimalCount.value))
            methods.input(fixedOutputValue)
        }
    },
}

interface StateType {
    mouseDown: boolean
    containerLeft: number
    containerRight: number
    focused: boolean
    mouseIn: boolean
    resizeObserver: ResizeObserver
}
const state = reactive<StateType>({
    mouseDown: false,
    containerLeft: 0,
    containerRight: 0,
    focused: false,
    mouseIn: false,
    resizeObserver: new ResizeObserver(methods.checkAndSetContainerPositionAndSize),
})

const minClientX = computed(() => {
    return state.containerLeft + 2
})
const maxClientX = computed(() => {
    return state.containerRight - 2
})
const sliderLeft = computed(() => {
    let rate = ((props.value ?? 0) - props.minValue) / (props.maxValue - props.minValue)
    return rate * (maxClientX.value - minClientX.value) + 2 + 'px'
})
const decimalCount = computed(() => {
    return Math.max(utils.getDecimalCount(props.minValue), utils.getDecimalCount(props.maxValue), utils.getDecimalCount(props.step))
})
const popShown = computed(() => {
    if (state.focused) {
        return true
    }
    if (state.mouseIn) {
        return true
    }
    return undefined
})

const sliderContainerRef = ref<HTMLDivElement>()
const elRef = ref<HTMLDivElement>()

onMounted(() => {
    state.resizeObserver.observe(sliderContainerRef.value!)
    methods.addEventListener()
})
onUnmounted(() => {
    state.resizeObserver.disconnect()
    methods.removeEventListener()
})

const emit = defineEmits<{
    (e: 'update:value', value?: number): void
}>()
useInput(props)
const formItemHook = useFormItem(props)
</script>